/**
 * @Author: lena
 * @Description:在Go1.7加入，专门用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号等相关操作
 * @Version: 1.0.0
 * @Date: 2021/9/17 15:28
 * 学习文档：https://www.liwenzhou.com/posts/Go/go_context/
 */

package lib

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// Context1 WithCancel 控制子协程的关闭
func Context1() {
	// func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
	ctx, cancel := context.WithCancel(context.Background()) // 根据当前context创建上下文
	defer cancel()                                          // 当调用该方法时 会关闭ctx的Done() 同时子程序的ctx.Done()的通道也会被关闭 (若执行完程序不cancel 会导致save的内存泄漏)
	for res := range save(ctx) {
		fmt.Println(res)
		if res == 10 { // 在返回的通道中接收到10的时候退出
			fmt.Println("我退出啦！")
			break // 父程序退出的时候 ctx上下文会关闭 所以save方法会执行case <-ctx.Done() 关闭子程序
		}
	}
}

// save 返回带有新Done通道的父节点的副本，当调用返回的cancel()方法时，会关闭上下文通道
func save(ctx context.Context) <-chan int { // 返回int型通道
	dst := make(chan int, 10) // 创建通道
	go func() {               // 开启一个协程 从i=0不断向通道dst中存值
		fmt.Println("创建协程")
		i := 0
		for {
			select {
			case <-ctx.Done(): // ctx已经关闭，结束程序
				return
			case dst <- i: // 向通道中存值
				i++
			}
			// fmt.Println(i)
		}
	}()
	return dst // 返回通道（可以通过监听这个通道接收值）
}

// Context2 WithDeadline 控制子进程关闭时间
func Context2() {
	// func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*1)) // 子ctx在5s后关闭
	defer cancel()                                                                           // 时间
	select {
	case <-ctx.Done():
		fmt.Println("over!") // go
	case <-time.After(time.Second * 5): // 父context在5s后才关闭
		fmt.Println("过了5s")
	}
}
func Context3() {
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5)) // 子ctx在5s后关闭
	defer cancel()                                                                           // 时间
	select {
	case <-ctx.Done():
		fmt.Println("over!")
	case <-time.After(time.Second * 1): // 父context在1s后才关闭
		fmt.Println("过了1s") // go
	}
}

var wg4 sync.WaitGroup

// Context4 WithTimeout
func Context4() {
	// func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) // 5s超时
	defer cancel()
	wg4.Add(1)
	go conn(ctx)
	wg4.Wait() // 等待wg.Done()
}
func conn(ctx context.Context) {
	for {
		fmt.Println("连接数据库")
		// ...省略连接数据库操作
		flag := false
		time.Sleep(time.Second)
		if !flag {
			fmt.Println("连接数据库失败 等待重新尝试")
		}
		select {
		case <-ctx.Done():
			fmt.Println("连接数据库超时")
			goto OVER
		default:
		}
	}
OVER:
	wg4.Done()
}

type LoginUser string

var wg5 sync.WaitGroup

// Context5 WithValue
func Context5() {
	// func WithValue(parent Context, key, val interface{}) Context
	ctx := context.WithValue(context.Background(), LoginUser("username"), "lena") // 传入key的是LoginUser类型,值为username
	wg5.Add(1)
	go get(ctx)
	wg5.Wait()
	fmt.Println("over!")
}
func get(ctx context.Context) {
	key := LoginUser("username") // 获取一个数据类型为LoginUser 值为username的变量
	value := ctx.Value(key)      // 根据key获取value
	fmt.Println(key, value)      // 输出：username lena
	wg5.Done()
}

func Context6() {
	ctx := context.TODO()
	value := ctx.Value("name")
	fmt.Println(value)
	value1, ok := ctx.Value("name").(string) // 不用ok会panic
	if !ok {
		fmt.Println("no data")
		return
	}
	fmt.Println(value1)
}
